﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace DenisVuyka.Controls.PropertyGrid.Controls
{
  //http://sharpinsights.wordpress.com/2007/09/
  public class DoubleEditor : Control
  {
    #region Fields

    private Point m_dragStartPoint;
    private Point m_lastDragPoint;

    private double m_changeValue;
    private double m_changeOffset;
    private bool m_isMouseDown = false;

    private const double DragTolerance = 2.0;

    #endregion Fields

    #region Constructors

    static DoubleEditor()
    {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(DoubleEditor), new FrameworkPropertyMetadata(typeof(DoubleEditor)));

      CommandManager.RegisterClassCommandBinding(typeof(DoubleEditor), new CommandBinding(Increase, OnIncrease));
      CommandManager.RegisterClassCommandBinding(typeof(DoubleEditor), new CommandBinding(Decrease, OnDecrease));
    }

    #endregion Constructors

    #region Dependency Property Fields

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleEditor), new PropertyMetadata(0.0, ValueChanged));

    public static readonly DependencyProperty SmallChangeProperty = DependencyProperty.Register("SmallChange", typeof(double), typeof(DoubleEditor), new PropertyMetadata(1.0));
    public static readonly DependencyProperty LargeChangeProperty = DependencyProperty.Register("LargeChange", typeof(double), typeof(DoubleEditor), new PropertyMetadata(1.0));
    public static readonly DependencyProperty DefaultChangeProperty = DependencyProperty.Register("DefaultChange", typeof(double), typeof(DoubleEditor), new PropertyMetadata(1.0));

    public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(DoubleEditor), new PropertyMetadata(0.0));
    public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(DoubleEditor), new PropertyMetadata(double.MaxValue));

    public static readonly DependencyProperty MaxPrecisionProperty = DependencyProperty.Register("MaxPrecision", typeof(int), typeof(DoubleEditor), new PropertyMetadata(0));

    public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register("IsDragging", typeof(bool), typeof(DoubleEditor), new PropertyMetadata(false));

    #endregion Dependency Property Fields

    #region RoutedCommand Fields

    public static RoutedCommand Increase = new RoutedCommand("Increase", typeof(DoubleEditor));
    public static RoutedCommand Decrease = new RoutedCommand("Decrease", typeof(DoubleEditor));

    #endregion RoutedCommand Fields

    #region RoutedEvent Handlers

    private static void OnIncrease(object sender, ExecutedRoutedEventArgs e)
    {
      DoubleEditor ve = (DoubleEditor)sender;

      ve.m_changeValue = ve.Value;
      ve.m_changeOffset = 0;
      ve.CalculateValue(ve.DefaultChange);
    }

    private static void OnDecrease(object sender, ExecutedRoutedEventArgs e)
    {
      DoubleEditor ve = (DoubleEditor)sender;

      ve.m_changeValue = ve.Value;
      ve.m_changeOffset = 0;
      ve.CalculateValue(-ve.DefaultChange);
    }

    #endregion RoutedEvent Handlers

    #region Properties

    [TypeConverter(typeof(LengthConverter))]
    public double Value
    {
      get { return (double)GetValue(ValueProperty); }
      set { SetValue(ValueProperty, value); }
    }

    public double SmallChange
    {
      get { return (double)GetValue(SmallChangeProperty); }
      set { SetValue(SmallChangeProperty, value); }
    }

    public double DefaultChange
    {
      get { return (double)GetValue(DefaultChangeProperty); }
      set { SetValue(DefaultChangeProperty, value); }
    }

    public double LargeChange
    {
      get { return (double)GetValue(LargeChangeProperty); }
      set { SetValue(LargeChangeProperty, value); }
    }

    public double Minimum
    {
      get { return (double)GetValue(MinimumProperty); }
      set { SetValue(MinimumProperty, value); }
    }

    public double Maximum
    {
      get { return (double)GetValue(MaximumProperty); }
      set { SetValue(MaximumProperty, value); }
    }

    public int MaxPrecision
    {
      get { return (int)GetValue(MaxPrecisionProperty); }
      set { SetValue(MaxPrecisionProperty, value); }
    }

    [Browsable(false)]
    public bool IsDragging
    {
      get { return (bool)GetValue(IsDraggingProperty); }
      set { SetValue(IsDraggingProperty, value); }
    }

    #endregion Properties

    #region Property Changed Callbacks

    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      DoubleEditor ve = (DoubleEditor)d;
      ve.Value = ve.EnforceLimitsAndPrecision((double)e.NewValue);
    }

    #endregion Property Changed Callbacks

    #region Base Class Overrides

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
      base.OnMouseDown(e);
      if (e.LeftButton == MouseButtonState.Pressed)
      {
        m_isMouseDown = true;
        m_dragStartPoint = e.GetPosition(this);

        base.Focus();
        base.CaptureMouse();

        e.Handled = true;
      }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      Point position = e.GetPosition(this);
      Vector vector = position - m_dragStartPoint;

      if (m_isMouseDown)
      {
        if (!IsDragging)
        {
          if (vector.Length > DragTolerance)
          {
            IsDragging = true;
            e.Handled = true;

            m_dragStartPoint = position;

            m_lastDragPoint = m_dragStartPoint;
            m_changeValue = Value;
            m_changeOffset = 0;
          }
        }
        else
        {
          Vector offset = position - m_lastDragPoint;
          double offsetLength = Math.Round(offset.Length);

          if (offsetLength != 0)
          {
            CalculateValue((offset.X > offset.Y) ? offsetLength : -offsetLength);
            m_lastDragPoint = position;
          }
        }
        e.Handled = true;
      }
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
      base.OnMouseUp(e);

      if (IsDragging || m_isMouseDown)
      {
        e.Handled = true;
        IsDragging = false;
        m_isMouseDown = false;
      }

      base.ReleaseMouseCapture();
    }

    #endregion Base Class Overrides

    #region Helpers

    private void CalculateValue(double chageValue)
    {
      //
      // Calculate the base ammount of chage based on...
      //
      // On Mouse Click & Control Key Press
      if ((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.None)
        chageValue *= SmallChange;
      // On Mouse Click & Shift Key Press
      else if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.None)
        chageValue *= LargeChange;
      else
        chageValue *= DefaultChange;

      m_changeOffset += chageValue;
      double newValue = m_changeValue + m_changeOffset;
      //
      // Make sure the change is line up with Max/Min Limits and set the precission as specified.
      Value = EnforceLimitsAndPrecision(newValue);

      return;
    }

    private double EnforceLimitsAndPrecision(double value)
    {
      return Math.Round(Math.Max(Minimum, Math.Min(Maximum, value)), MaxPrecision);
    }

    #endregion Helpers
  }
}